// https://r2coding.com/#/README?id=%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f 查看可视化正则
// 合法的命名规则(a-z/A-Z/_ 开头的,后面的随意) 如：div/_div
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
// 匹配符合ncname:ncname规则的/单纯符合ncname规则 如：div / namesapce:div
// namesapce:div是web components用的,一般用不上
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
// 匹配到的是开始标签的开头部分,以 < 开始的 qnameCapture, 如：<div / <namesapce:div
const startTagOpen = new RegExp(`^<${qnameCapture}`);
// 匹配到的是开始标签的结束部分/自闭合标签,以 />或 > 结尾的,如 <img的"/>" 或 <div的">"
const startTagClose = /^\s*(\/?)>/;
// 匹配到的是闭合标签,以 </ 开始的 qnameCapture 以只有一个 > 结尾的 如: </div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);
// 匹配属性名,以属性名:不包含 "'<>/= ,属性值:符合引号(""/'')用法/没有引号的/直接没有属性值和=,等号左右可以有空格 如 class = 'abc' / id=123 / disabled
// 匹配到的第一个分组就是key,value是第3/4/5分组
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配以"{{"开始"}}"结束的内容,中间可以有换行和空格, 如：{{ name }}
export const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g;
// vue3用的不是正则,是一个一个字符分析的
// 节点类型
export const ELEMENT_TYPE = 1;
export const TEXT_TYPE = 3;

// 对模板进行编译处理,最终要转换成一种抽象语法树
export function parseHTML(html) {
  // 节点栈
  const stack = [];
  // 指向的是栈中的最后一个(父节点)
  let currentParent;
  // 根节点
  let root;
  // 创建一个标签类型节点
  function createASTElement(tag, attrs) {
    return {
      tag,
      type: ELEMENT_TYPE,
      attrs,
      children: [],
      parent: null
    }
  }
  // 开始标签入栈
  function start(tag, attrs) {
    let node = createASTElement(tag, attrs);
    if (!root) {
      // 如果没有root,就说明这个节点是根节点
      root = node;
    }
    // 压入栈中
    stack.push(node);
    if (currentParent) {
      // 定义父亲
      node.parent = currentParent;
      // 父亲添加个儿子
      currentParent.children.push(node);
    }
    // 父节点指向此节点
    currentParent = node;
  }
  // 文本直接插入父节点的children中
  function chars(text) {
    // 把空字符干掉
    text = text.replace(/\s/g, '');
    text && currentParent.children.push({
      type: TEXT_TYPE,
      text,
      parent: currentParent
    });
  }
  // 结束标签出栈
  function end(tag) {
    let node = stack.pop();
    // if(node!=tag){
    //   return;
    // }
    // 重新指向最后一个
    currentParent = stack[stack.length - 1] || null;
  }
  // 消费匹配到的字符串
  function advance(n) {
    html = html.substring(n);
  }
  // 解析开始标签
  function parseStartTag() {
    // 匹配开始标签
    const start = html.match(startTagOpen);
    if (start) {
      const match = {
        // 标签名
        tagName: start[1],
        attrs: []
      }
      advance(start[0].length);
      let attr, end;
      // 如果不是开始标签的结尾并且能匹配到标签属性,就一直匹配下去
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        // 匹配到attr就消费掉
        advance(attr[0].length);
        // 添加属性
        match.attrs.push({
          name: attr[1],
          // 有可能disabled这种的没有value,默认给个true
          value: attr[3] || attr[4] || attr[5] || true
        })
      }
      if (end) {
        // 匹配到结束标签就消费掉
        advance(end[0].length);
      }
      return match;
    }
    // 不是开始标签
    return false;
  }
  while (html) {
    // textEnd = 0,说明是一个开始标签/结束标签
    // textEnd > 0,说明是文本
    let textEnd = html.indexOf('<');
    if (textEnd === 0) {
      // 如果是开始尝试是否是开始标签
      const startTagMatch = parseStartTag();
      // 如果是开始标签就开始下一次循环
      if (startTagMatch) {
        // 开始标签入栈
        start(startTagMatch.tagName, startTagMatch.attrs);
        continue;
      }
      // 否则就是结束标签
      let endTagMatch = html.match(endTag);
      if (endTagMatch) {
        // 找到了就消费掉
        advance(endTagMatch[0].length);
        // 结束标签出栈
        end(endTagMatch[1]);
        continue;
      }
    }
    if (textEnd > 0) {
      // 获取文本内容
      const text = html.substring(0, textEnd);
      // 是文本就把文本部分截掉
      if (text) {
        chars(text);
        advance(text.length);
      }
    }
  }
  return root;
}